package server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.math.BigInteger;
import java.net.Socket;
import java.net.SocketException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Vector;
import beans.*;
import java.io.EOFException;
import java.io.InterruptedIOException;
import java.util.Random;

/**********************************************************************
 *
 * Filename: Client_Handler.java
 *
 * File Description:
 * Method to handle new client threads
 *
 * Author:
 * @author Tim Schmalz
 * Date Created:
 *  Oct 9, 2009
 *
 * Modified:
 * @author Brandon Fulco
 * Date Modified:
 *  Oct 25, 2009
 **********************************************************************/
public class Client_Handler extends Thread
{
	private Socket clientSocket = null;
	private final int TIMEOUT_TIME = 100000; //Set time out to 100 seconds

	//Hash Map to hold of the current drafts
	private static HashMap<Integer,Draft_Data> draft_map = new HashMap<Integer,Draft_Data>();

	//Constants
	private final boolean LOGGED_IN = true;

	//Hold User Team information
	int teamId;
	int leagueId;
	int leagueSize;
	private boolean wait = true;
	private boolean done = false;
	private ObjectInputStream serialIn;
	private ObjectOutputStream serialOut;
	private Draft_Bean readBean;
	private Draft_Bean writeBean;

	/**
	 * Creates a new handler for a given client
	 *
	 * @param client - Socket that the client is listening to
	 */
	public Client_Handler(Socket client)
	{
		clientSocket = client;
                //Set the timeout time
		try
		{
			clientSocket.setSoTimeout(TIMEOUT_TIME);
		}
		catch (SocketException ex)
		{
			System.err.println("Unable to set timeout time");
		}
	}

	/**
	 * Runs the clients thread
	 *
	 */
	@Override
	public void run()
	{
                //Is client timed out?
                boolean isTimedOut = false;
		//Begin to handle client
                User_Bean user = new User_Bean();
		//Draft utility class
		Database_Service databaseService = new Database_Service();
		databaseService.connectToDatabase();
                try
		{
                        // Get I/O streams from the client
			serialIn = new ObjectInputStream (clientSocket.getInputStream());
			serialOut = new ObjectOutputStream (new PrintStream(clientSocket.getOutputStream()));
			try
			{
                                Object obj = new Object();
                                try
                                {
                                    // Read an object
                                    obj = serialIn.readObject();
                                }
                                catch(InterruptedIOException iioe)
                                {
                                    System.err.println("User Timed Out");
                                    isTimedOut = true;
                                }
                                if(!isTimedOut)
                                {
                                    if (obj instanceof User_Bean)
                                    {
    					user = (User_Bean)obj;
                                    }
                                    else
                                    {
					throw new Exception("Unsuported Serialized Class");
                                    }
                                }
			}
			catch (IOException ioe)
			{
				System.err.println("TCP input failure:");
				System.err.println(ioe);
				throw new Exception("Input Failure: Network I/O Error");
			}
			catch (Exception e)
			{
				System.err.println(e.getMessage());
				throw e;
			}
                        //Continue with login if user message recieved
                        if(!isTimedOut)
                        {
                            System.out.println("Attempting to login " + user.getUserName());
                            user = login(user, databaseService);
                            if(user != null)
                            {
                                    System.out.println(user.getUserName()+" has logged in");
                                    Vector<Player_Bean> players = databaseService.queryPlayers();
                                    boolean isLoggedIn = LOGGED_IN;
                                    
                                    //Check to see if user has an existing draft
                                    Draft_Data draftData = draft_map.get(user.getLeagueId());
                                    if(draftData == null)
                                    {
                                            //Give thread an identity
                                            teamId = user.getTeamId();
                                            leagueId = user.getLeagueId();
                                            //Create a new draft in the map
                                            draftData = new Draft_Data();
                                            draftData.addNewTeam(this);
                                            draftData.setPlayersList(players);
                                            draft_map.put(user.getLeagueId(), draftData);
                                            System.out.println("Team " + user.getTeamId() + "(League: " + user.getLeagueId() + ") has created a new draft");
                                    }
                                    else
                                    {
                                            //Give thread an identity
                                            teamId = user.getTeamId();
                                            leagueId = user.getLeagueId();
                                            //Check for double login
                                            if(!draftData.isDrafterPresent(teamId))
                                            {
                                                //Add client to their existing draft
                                                draftData.addNewTeam(this);
                                                System.out.println("Team " + user.getTeamId() + "(League: " + user.getLeagueId() + ") has joined its draft");
                                            }
                                            else
                                            {
                                                isLoggedIn = !LOGGED_IN;
                                            }
                                    }
                                    //Tell user they are logged in
                                    user.setLoggInStatus(isLoggedIn);
                                    if(!isLoggedIn)
                                    {
                                        user.setErrorString("User already logged in");
                                    }
                                    serialOut.writeObject(user);
                                    serialOut.flush();
                                    if(isLoggedIn)
                                    {
                                        serialOut.writeObject(players);
                                        serialOut.flush();
                                        serialOut.writeObject(databaseService.queryTeams(user.getLeagueId()));
                                        serialOut.flush();
                                        //Get the size of the league
                                        leagueSize = databaseService.getSizeOfLeague(leagueId);
                                        //Wait for users
                                        while(draftData.getDraftList().size() < leagueSize){sleep(500);}
                                        Drafters_Wrapper drafters = draftData.getCurrentDrafter();
                                        serialOut.writeObject(new Draft_Bean(-1,drafters.getOnClockId(),drafters.getOnDeckId(),drafters.getInTheHoleId()));
                                        //Begin Draft
                                        System.out.println("Beginning Draft");
                                        runDraft(draftData, databaseService);
                                    }
                            }
                            else
                            {
                                    System.out.println("Invalid User: Failed login");
                                    User_Bean failed = new User_Bean();
                                    failed.setLoggInStatus(!LOGGED_IN);
                                    failed.setErrorString("Invaild Login");
                                    serialOut.writeObject(failed);
                                    serialOut.flush();
                            }
                        }
			// Close client connections
			databaseService.closeConnection();
			clientSocket.close();
		}
        	catch(Exception e)
		{
			System.out.println("Missed client.");
			System.err.println(e);
			e.printStackTrace();
		}
	}

	/**
	 * Checks the use against the database to see if they exist
	 *
	 * @param userName - User's user name
	 * @param Password - User's password
	 * @return return - User_Bean (Null if user not in database)
	 * @throws NoSuchAlgorithmException
	 */
	private User_Bean login(User_Bean user, Database_Service databaseService) throws NoSuchAlgorithmException
	{
		//Encrypting Password
		String toEnc = user.getPassword(); // Value to encrypt
		MessageDigest mdEnc;
		mdEnc = MessageDigest.getInstance("MD5");
		// Encryption algorithm
		mdEnc.update(toEnc.getBytes(), 0, toEnc.length());
		String md5 = new BigInteger(1, mdEnc.digest()).toString(16); // Encrypted string
		user.setPassword(md5);
		//Check if user in the database
		user = databaseService.login(user);
		return user;
	}

	/**
	 * Handles the draft
	 *
	 * @param draftData - Class that holds the league's draft data
	 */
    @SuppressWarnings("empty-statement")
	private void runDraft(Draft_Data draftData, Database_Service databaseService)
	{
		Vector<Client_Handler> drafters = draftData.getDraftList();
                Vector<Player_Bean> players = draftData.getPlayersList();
		boolean endDraft = false;
		int size = 0;
                boolean isTimedOut = false;

		while(true)
		{
			writeBean = new Draft_Bean(-1,-1,-1,-1);
			try
			{
                                //Check for user time out
                                if(!isTimedOut)
                                {
                                    try
                                    {
                                        readBean = (Draft_Bean)serialIn.readObject();
                                    }
                                    catch(InterruptedIOException iioe)
                                    {
                                        System.err.println("Team:" + teamId + "League:" + leagueId +" Timed Out");
                                        isTimedOut = true;
                                    }
                                    catch(EOFException EOF)
                                    {
                                        System.err.println("Team:" + teamId + "League:" + leagueId +" Has Dropped");
                                        isTimedOut = true;
                                    }
                                    catch(Exception ex)
                                    {
                                        System.err.println("An unkown error has occured during Team:" + teamId + "League:" + leagueId +" pick \n User has been dropped");
                                        System.err.println(ex);
                                        isTimedOut = true;
                                    }
                                }
                                //On time out draft for user
                                if(isTimedOut)
                                {
                                    //Draft Random Player on Timeout
                                    if(draftData.getCurrentDrafter().getOnClockId() == teamId)
                                    {
                                        Random generator = new Random();
                                        int draftedPlayerId = players.get(generator.nextInt(players.size()-1)).getPlayerId();
                                        readBean = new Draft_Bean(draftedPlayerId,-1,-1,-1);
                                    }
                                    else
                                    {
                                        readBean = new Draft_Bean(-1,-1,-1,-1);
                                    }
                                }

				if(readBean.getPlayerId() == -1)
				{
					while(wait)
					{
						Thread.yield();
						Thread.sleep(1000);
					}
					wait = true;
				}
				else
				{
					size = databaseService.getSizeRoster(teamId);
                                        //Remove player from server's list
                                        int i = 0;
                                        System.out.println(readBean.getPlayerId());
                                        while (players.get(i).getPlayerId() != readBean.getPlayerId())
                                        {
                                            i++;
                                        }
                                        players.remove(i);
                                        //Enter player into database
					if(size < 6)
						databaseService.addPlayerToRoster(readBean.getPlayerId(), teamId, 1);
					else if (size == 9)
					{
						databaseService.addPlayerToRoster(readBean.getPlayerId(), teamId, -1);
						endDraft = true;
					}
					else
                                        {
						databaseService.addPlayerToRoster(readBean.getPlayerId(), teamId, -1);
                                        }
					draftData.incrementDrafter();
					for(int j = 0; j < draftData.getDraftList().size(); j++)
					{
						try
						{
							drafters.elementAt(j).setPlayerDraftBean(readBean.getPlayerId());
							drafters.elementAt(j).setWait(false);
						}
						catch (Exception ex)
						{}// If here then drafter closed, not a problem.
					}
					wait = true;
				}
				Drafters_Wrapper nextDrafters = draftData.getCurrentDrafter();
				writeBean.setTurnId(nextDrafters.getOnClockId());
				writeBean.setOnDeckId(nextDrafters.getOnDeckId());
				writeBean.setInTheHoleId(nextDrafters.getInTheHoleId());
				serialOut.writeObject(writeBean);
				serialOut.flush();
				if (endDraft)
					done = true;
				int i = 0;
				for(; i < drafters.size() && drafters.elementAt(i).getDone(); i++);
				if (i == drafters.size())
				{
					databaseService.setDraftToDone(leagueId);
					break;
				}

			}
			catch(Exception ex)
			{
				System.err.println("An expection has been thrown in runDraft");
				ex.printStackTrace();
			}
		}
		try
		{
			Thread.sleep(500);
			serialOut.reset();
			writeBean.setTurnId(-1);
			writeBean.setPlayerId(-1);
			writeBean.setOnDeckId(-1);
			writeBean.setInTheHoleId(-1);
			serialOut.writeObject(writeBean);
			serialOut.flush();
			Thread.sleep(2000);
		}
		catch (Exception ex)
		{
			System.err.println("An expection has been thrown in runDraft");
			System.err.println(ex);
		}
	}

        /**
         * Tells thread to wait
         *
         * @param wait - wait status
         */
	public void setWait(boolean wait)
	{
		this.wait = wait;
	}

        /**
         * Writes the last drafted to a draft bean
         *
         * @param playerid - Last drafted player id
         */
	public void setPlayerDraftBean(int playerid)
	{
		writeBean.setPlayerId(playerid);
	}

        /**
         * Is the draft done
         *
         * @return draft status
         */
	public boolean getDone()
	{
		return done;
	}

        /**
         * Gets the handlers current team id
         *
         * @return The handlers team id
         */
        public int getTeamId()
        {
            return teamId;
        }
}
